package Robots;

// Bereitstellen der Einheitenumrechnungen
import HelpingClasses.PhysikEinheiten;
// Bereitstellen der mathematischen Methoden aus den Standardbibliotheken
import java.math.*;
// Bereitstellen der Methoden zur Ziffernformatierung
import java.text.*;

/**
 * <strong>direkte Unterklasse von Roboter</strong><br />
 * Eine Instanz von MobileRoboter (im Folgenden "mobiler Roboter") ist zunaechst einmal
 * ein Roboter (Superklasse), hat aber weitere Eigenschaften wie einen Tank (mit maximalem
 * Tankvolumen und gegenwaertigem Tankfuellstand), einem maximalen und gegenwaertigen 
 * Tempo sowie einem Verbrauch. Daraus lassen sich dann die verbleibende Laufzeit und die
 * maximale bzw. verbleibende Reichweite ermitteln.<br />
 * <br />
 * <strong>TODO</strong>
 * @author Torbjoern Klatt
 */
public class MobileRoboter extends Roboter {
    // Instanzvariablen
    private double tempo_max, tempo_current = 0, verbrauch, tank_max, tank_current = 0;
    private int strukturpkt;
    
    // Klassenvariablen
    DecimalFormat df = new DecimalFormat("#0.00");
    
    //*******************************
    // Konstruktoren
    /**
     * erzeugt ein Objekt vom Typ MobileRoboter
     * @param s Name
     * @param bj Baujahr
     * @param tempo_max maximale Geschwindigkeit in Meter/Sekunde; wenn kleiner gleich 0 => 1
     * @param tnk_max maximales Tankvolumen in Liter; wenn kleiner gleich 0 => 1
     * @param verb Verbrauch in Liter/Kilometer; wenn kleiner gleich 0 => 1
     */
    public MobileRoboter(String s, int bj, double tempo_max, double tnk_max, double verb) {
        super(s, bj);
        this.tempo_max = tempo_max;
        this.tank_max = tnk_max;
        this.verbrauch = verb;
        if(verb <= 0) {
            this.verbrauch = 1;
        }
        if(tempo_max <= 0) {
            this.tempo_max = 1;
        }
        if(tnk_max <= 0) {
            this.tank_max = 1;
        }
    }

    //*******************************
    // Getter
    /**
     * @return Laufzeit bei vollem Tank und maximalem Tempo
     */
    public double getLaufzeit_min() {
        return this.tank_max/this.verbrauch/this.tempo_max;
    }
    
    /**
     * @return verbleibende Laufzeit in Stunden bei gegenwaertigem Tankfuellstand und Tempo
     */
    public double getLaufzeit_remaining() {
        if(this.tempo_current > 0) {
            return this.tank_current/this.verbrauch/this.tempo_current;
        }
        else {
            return 0;
        }
    }

    /**
     * @return maximale Reichweite in Kilometer
     */
    public double getReichweite_max() {
        return this.tank_max/this.verbrauch;
    }

    /**
     * @return verbleibende Reichweite in Kilometer bei gegenwaertigem Tankfuellstand
     */
    public double getReichweite_remaining() {
        return this.tank_current/this.verbrauch;
    }
    
    /**
     * @return Strukturpunkte
     */
    public int getStrukturpunkte() {
        return this.strukturpkt;
    }

    /**
     * @return maximales Tankvolumen
     */
    public double getTank_max() {
        return this.tank_max;
    }

    /**
     * @return gegenwaertiger Tankfuellstand
     */
    public double getTank_current() {
        return this.tank_current;
    }

    /**
     * @return maximale Geschindigkeit in Meter/Sekunde
     */
    public double getTempo_max() {
        return this.tempo_max;
    }

    /**
     * @return gegenwaertige Geschwindigkeit in Meter/Sekunde
     */
    public double getTempo_current() {
        return this.tempo_current;
    }
    
    /**
     * @return Verbrauch in Liter
     */
    public double getVerbrauch() {
        return this.verbrauch;
    }

    //*******************************
    // Setter    
    /**
     * Sind die Strukturpunkte kleiner gleich 0, so wird der Roboter abgeschaltet
     * @param i Strukturpunkte
     */
    public void setStrukturpunkte(int i) {
        if(i > 0) {
            this.strukturpkt = i;
        }
        else {
            this.strukturpkt = 0;
            this.switchOff();
        }
    }
    
    /**
     * @param tnk_max maximales Tankvolumen in Liter; wenn kleiner gleich 0 => 1
     */
    public void setTank_max(double tnk_max) {
        this.tank_max = tnk_max;
        if(tnk_max <= 0) {
            this.tank_max = 1;
        }
    }
    
    /**
     * @param tnk_curr gegenwaertiger Tankfuellstand
     */
    public void setTank_current(double tnk_curr) {
        this.tank_current = tnk_curr;
    }

    /**
     * @param tempo_max maximale Geschindigkeit in Meter/Sekunde; wenn kleiner gliech 0 => 1
     */
    public void setTempo_max(double tempo_max) {
        this.tempo_max = tempo_max;
        if(tempo_max <= 0) {
            this.tempo_max = 1;
        }
    }

    /**
     * Wenn temp_cur groesser als maximale Geschindigkeit ist, wird tempo_current auf
     * tempo_max gesetzt.
     * @param temp_cur gegenwaertige Geschwindigkeit in Meter/Sekunde
     */
    public void setTempo_current(double temp_cur) {
        this.tempo_current = Math.min(temp_cur,this.tempo_max);
    }

    /**
     * @param verb Verbrauch in Liter/Kilometer; wenn kleiner gleich 0 => 1
     */
    public void setVerbrauch(double verb) {
        this.verbrauch = verb;
        if(verb <= 0) {
            this.verbrauch = 1;
        }
    }

    /**
     * schaltet den Roboter ein und setzt die Geschwindigkeit auf 1
     */
    @Override
    public void switchOn() {
        super.switchOn();
        setTempo_current(1);
    }

    /**
     * schaltet den Roboter ab und setzt die Geschwindigkeit auf 0
     */
    @Override
    public void switchOff() {
        super.switchOff();
        setTempo_current(0);
    }

    /**
     * fuellt den Tank um add
     * @param add zu tankende Treibstoffmenge in Liter
     * @throws IllegalArgumentException wenn add kleiner oder gleich 0
     */
    public void tanken(double add) throws IllegalArgumentException {
        if(add > 0) {
            setTank_current(Math.min((this.tank_current+add),this.tank_max));
        }
        else {
            throw new IllegalArgumentException(
                    "Beim Tanken entnimmt man dem Tank keinen Treibstoff.");
        }
    }

    /**
     * fuellt den Tank zum Maximum
     */
    public void vollTanken() {
        this.tanken(this.tank_max);
    }

    //*******************************
    // Instanzmethoden
    /**
     * Berechnet den neuen Tankfuellstand nach Zuruecklegen der Distanz bei aktuellem Tempo
     * in der angegebenen Zeit und gibt kurze Statusmeldung in Stdout aus.
     * @param t Zeit in Stunden
     */
    public void moveForHours(double t) {
        PhysikEinheiten physein = new PhysikEinheiten(this.tempo_current, 0, t);
        System.out.println("Es wird versucht sich "+df.format(t)+"h lang fortzubwegen.");
        // zu zuruecklegende Distanz nach der Zeit und gegenwaertigem Tempo berechnen
        physein.setKmeter(physein.ms2kmh()*physein.getH());
        // noch genuegend Treibstoff vorhanden
        if(t >= this.getLaufzeit_remaining()) {
            // zurueckgelegte Distanz und benoetigte Zeit ausgeben
            System.out.println("Es wurden "+df.format(physein.getKmeter())+" Kilometer " +
                    "in "+df.format(t)+" Stunden zureuckgelegt.");
            // neuen Tankfuellstand berechnen
            this.tank_current = this.tank_current-(this.verbrauch*physein.getKmeter());
            // und Tenkfuellstand ueberpruefen
            this.checkTank();
        }
        // zu wenige Treibstoff
        else {
            // zurueckgelegte Distanz berechnen
            physein.setKmeter(this.getReichweite_remaining());
            // Tankfuellstand entsprechend setzen und Meldung ausgeben
            this.tank_current = 0;
            System.out.println("Zu wenig Treibstoff fuer angestrebte Laufzeit." +
                    " Es wurden "+df.format(physein.getKmeter())+" Kilometer in "
                    +df.format(physein.getH())+" Stunden zurueckgelegt.");
            // Tankfuellstand ueberpruefen
            this.checkTank();
        }
    }
    
    /**
     * Berechnet den neuen Tankfuellstand nach zuruecklegen der angegebenen Kilometern,
     * die benoetigte Zeit und gibt kurze Statusmeldung in Stdout aus.
     * @param d Distanz in Kilometer
     */
    public void moveForDistance(double d) {
        PhysikEinheiten physein = new PhysikEinheiten(this.tempo_current, d, 0);
        System.out.println("Es wird versucht "+df.format(d)+"km zurueckzulegen.");
        // benoetigte Zeit berechnen
        physein.setH(d/this.tempo_current);
        // noch genuegend Treibstoff vorhanden
        if(d <= this.getReichweite_remaining()) {
            // zurueckgelegte Distanz und benoetigte Zeit ausgeben
            System.out.println("Es wurden "+df.format(d)+" Kilometer " +
                    "in "+df.format(physein.getH())+" Stunden zureuckgelegt.");
            // neuen Tankfuellstand berechnen
            this.tank_current = this.tank_current-(this.verbrauch*d);
            // und Tenkfuellstand ueberpruefen
            this.checkTank();
        }
        // zu wenig Treibstoff
        else {
            // zurueckgelegte Distanz berechnen
            d = this.getReichweite_remaining();
            // Tankfuellstand entsprechend setzen und Meldung ausgeben
            System.out.println("Zu wenig Treibstoff fuer angestrebte Distanz. Es wurden "+
                    df.format(d)+" Kilometer in "+df.format(this.getLaufzeit_remaining())
                    +" Stunden zurueckgelegt.");
            this.tank_current = 0;
            // Tankfuellstand ueberpruefen
            this.checkTank();
        }
    }
    
    /**
     * Tankfuellstand ueberpruefen. Gibt Statusmeldung in Stdout aus.
     */
    public void checkTank() {
        if(this.tank_current <= 0) {
            this.tank_current = 0;
            System.out.println("Der Tank ist leer. Roboter wird abgeschaltet.");
            this.switchOff();
        }
        else {
            System.out.println("Im Tank sind noch "+df.format(this.tank_current)
                    +" Liter Treibstoff.");
        }
    }
    
    /**
     * @return String mit name, baujahr, status, gegenwaertigem und maximalem Tempo,
     * gegenwaertigem Tankfuellstand und maximalem Tankvolumen, Verbrauch, verbleibende
     * Laufzeit und maximale Reichweite
     */
    @Override
    public String toString() {
        return "Mobiler Roboter:\n\tName: "+this.getName()
                +"\n\tID: "+this.getID()
                +"\n\tBaujahr: "+this.getBaujahr()
                +"\n\tEin/Aus: "+this.isOn()
                +"\n\tTempo: "+df.format(this.tempo_current)+"m/s / "
                    +df.format(this.tempo_max)+"m/s"
                +"\n\tTank: "+df.format(this.tank_current)+"L / "
                    +df.format(this.tank_max)+"L"
                +"\n\tVerbrauch: "+df.format(this.verbrauch)+"L/km"
                +"\n\tLaufzeit: "+df.format(this.getLaufzeit_remaining())+"h"
                +"\n\tReichweite: "+df.format(this.getReichweite_remaining())+"km / "
                    +df.format(this.getReichweite_max())+"km";
    }

    /**
     * @param obj
     * @return true wenn obj vom Typ MobileRoboter ist und sowohl Name, Baujahr, 
     * Tankvolumen, maximales Temp und Verbrauch identisch sind, sonst false
     */
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof MobileRoboter &&
                this.getName().equals(((MobileRoboter)obj).getName()) &&
                this.getBaujahr() == ((MobileRoboter)obj).getBaujahr() &&
                this.tank_max == ((MobileRoboter)obj).tank_max &&
                this.tempo_max == ((MobileRoboter)obj).tempo_max &&
                this.verbrauch == ((MobileRoboter)obj).verbrauch) {
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * @return einen Hashcode fuer diesen MobileRoboter
     */
    @Override
    public int hashCode() {
        int hash = 3;
        hash = 53 * hash + (int) (Double.doubleToLongBits(this.tempo_max) ^
                (Double.doubleToLongBits(this.tempo_max) >>> 32));
        hash = 53 * hash + (int) (Double.doubleToLongBits(this.tempo_current) ^
                (Double.doubleToLongBits(this.tempo_current) >>> 32));
        hash = 53 * hash + (int) (Double.doubleToLongBits(this.verbrauch) ^
                (Double.doubleToLongBits(this.verbrauch) >>> 32));
        hash = 53 * hash + (int) (Double.doubleToLongBits(this.tank_max) ^
                (Double.doubleToLongBits(this.tank_max) >>> 32));
        hash = 53 * hash + (int) (Double.doubleToLongBits(this.tank_current) ^
                (Double.doubleToLongBits(this.tank_current) >>> 32));
        return hash;
    }
}